import math
from bmpHeader import BitmapFileHeader, DIB, BmpData
from mytools import bytes2int, int2bytes, palette_gray


class BMP(BmpData):
    def __init__(self, stuff):
        # BitmapFileHeader.__init__(self,pic)
        # DIB.__init__(self,pic)
        if isinstance(stuff, BMP):
            self.copy(stuff)
        else:
            super(BMP, self).__init__(stuff)

    def showAttribute(self):
        print("Bftype ", self.bfType)
        print("bit count ", self.bit_count)
        print("file size", self.size)
        print("compression ", self.compression)
        print("Size :{} x {} ".format(self.width, self.height))
        print("image size :", self.size_image)
        print("bfOffbit ", self.offBits)
        print("header size ", self.head_size)
        print("biClrUsed", self.clr_used)
        print("x_PelsPerMeter {} y_PelsPerMeter {}".format(
            self.x_PelsPerMeter, self.y_PelsPerMeter))

    # Bilinear interpolation
    def __bili(self, new_image, dstWidth, dstHeight):
        w_ratio = self.width * 1.0 / dstWidth
        h_ratio = self.height * 1.0 / dstHeight
        for dstY in range(dstHeight):
            tempY = (dstY + 0.5) * h_ratio - 0.5
            Y0 = int(tempY)
            u = tempY - Y0
            Y0 = min(Y0, self.height - 2)
            Y1 = Y0 + 1
            op_u = 1 - u
            for dstX in range(dstWidth):
                tempX = (dstX + 0.5) * h_ratio - 0.5
                X0 = int(tempX)
                v = tempX - X0
                X0 = min(X0, self.width - 2)
                X1 = X0 + 1
                op_v = 1 - v

                w0 = op_u * op_v
                w1 = u * op_v
                w2 = op_u * v
                w3 = u * v

                pixel00 = self.data[Y0][X0]
                pixel01 = self.data[Y0][X1]
                pixel10 = self.data[Y1][X0]
                pixel11 = self.data[Y1][X1]

                for channel in range(3):
                    temp = bytes2int(pixel00[channel]) * w0 + bytes2int(pixel10[channel]) * w1 + bytes2int(
                        pixel01[channel]) * w2 + bytes2int(pixel11[channel]) * w3
                    byteData = int2bytes(int(temp), 1)
                    new_image.data[dstY][dstX][channel] = byteData

    def __nni(self, new_image, dstWidth, dstHeight):
        w_ratio = self.width * 1.0 / dstWidth
        h_ratio = self.height * 1.0 / dstHeight
        temp_width = self.width - 1
        temp_height = self.height - 1
        for dstY in range(dstHeight):
            # round顺便解决了向下溢出
            srcY = round((dstY + 0.5) * h_ratio - 0.5)
            if srcY > temp_height:
                srcY -= 1
            for dstX in range(dstWidth):
                srcX = round((dstX + 0.5) * w_ratio - 0.5)
                if srcX > temp_width:
                    srcX -= 1
                new_image.data[dstY][dstX] = self.data[srcY][srcX]

    # 需要的时候再去读像素数据，我做成了装饰器，其实没太大意义。写着玩的
    # parse 是类方法(我认为的)，然而他无法被继承
    def __parse(func):
        def wrapper(*args, **kwargs):
            instance = args[0]
            if len(instance.data) != 0:  # 已经读入了
                return func(*args, **kwargs)
            elif instance.bit_count == 24:
                instance.parse_24()
            elif instance.bit_count == 8:
                instance.parse_8()
            return func(*args, **kwargs)
        return wrapper

    # 仅支持24位图
    @__parse
    def resize(self, dstWidth, dstHeight):
        # 预先地开辟一块区域来，不知道效率会不会好些？

        # 用这种乘号极其坑爹，大家可以探索一下
        # new_image.data=[[[b'\x00']*3]*dstWidth]*dstHeight

        new_image = BMP(self)
        new_image.data = [[[b'\x00', b'\x00', b'\x00']
                           for x in range(dstWidth)]for y in range(dstHeight)]

        self.__bili(new_image, dstWidth, dstHeight)

        new_image.width = dstWidth
        new_image.height = dstHeight
        new_image.update_padding_num()
        new_image.update_file_size()

        new_image.updateBytes()
        new_image.updateInt()
        new_image.generate_24()
        return new_image

    @__parse
    def show_data(self):
        print(self.data)

    def showColor_table(self):
        self.pic.seek(54)
        print(self.offBits)
        self.palette = []
        for entry in range(256):
            unit = []
            for i in range(4):
                unit.append(bytes2int(self.pic.read(1)))
            self.palette.append(unit)
        print(self.palette)

    # 我有想过把generate_head做成装饰器的，不过从传参的角度看，不太好实现
    def generate_head(self, file_name):
        file = open(file_name, 'wb+')
        # reconstruct File Header
        file.write(self.bfType)
        file.write(self.bfSize)
        file.write(self.bfReserved1)
        file.write(self.bfReserved2)
        file.write(self.bfOffBits)
        # reconstruct bmp header
        file.write(self.biSize)
        file.write(self.biWidth)
        file.write(self.biHeight)
        file.write(self.biPlanes)
        file.write(self.biBitCount)
        file.write(self.biCompression)
        file.write(self.biSizeImage)
        file.write(self.biXPelsPerMeter)
        file.write(self.biYPelsPerMeter)
        file.write(self.biClrUsed)
        file.write(self.biClrImportant)
        return file

    # 填充部分，我都以0填充，实际应该并非如此
    # 生成的图片为24位图
    def generate_24(self, file_name='result.bmp'):
        file = self.generate_head(file_name)
        for row in range(self.height):
            for col in range(self.width):
                for channel in range(3):
                    file.write(self.data[row][col][channel])
            # for padding in range(self.padding_num):
            #     file.write(b'\x00')
            # 下面这种方法快不少
            file.write(b'\x00' * self.padding_num)
        file.close()

    # 生成的图片为8位图
    def generate_8(self, file_name='result.bmp'):
        file = self.generate_head(file_name)
        file = self.generate_color_table(file)
        for row in self.data:
            for entry in row:
                file.write(entry)
            file.write(b'\x00' * self.padding_num)
        file.close()

    # 写入灰度图的调色板
    def generate_color_table(self, file):
        if self.bit_count == 8:
            # palette_gray是一个占用大量字节的比特字符串
            color_table = palette_gray
            file.write(color_table)
        return file

    @__parse
    def graying(self):
        new_image = BMP(self)
        new_image.data = [[b'\x00'for x in range(
            self.width)]for y in range(self.height)]
        for y in range(self.height):
            row = []
            for x in range(self.width):
                b = bytes2int(self.data[y][x][0])
                g = bytes2int(self.data[y][x][1])
                r = bytes2int(self.data[y][x][2])
                gray = (299 * r + 587 * g + 114 * b) // 1000
                new_image.data[y][x] = (int2bytes(gray, 1))

        new_image.bit_count = 8
        new_image.offBits=1078
        new_image.update_padding_num()
        new_image.update_file_size()
        new_image.updateBytes()
        new_image.updateInt()
        new_image.generate_8()
        return new_image

    # 逆时针
    @__parse
    def rotate(self, theta=90):
        sin_theta = math.sin(math.radians(theta))
        cos_theta = math.cos(math.radians(theta))

        new_image = BMP(self)
        new_image.width = abs(
            round(self.width * cos_theta + self.height * sin_theta))
        new_image.height = abs(
            round(self.width * sin_theta + self.height * cos_theta))

        new_image.data = [[[b'\x00', b'\x00', b'\x00']for x in range(
            new_image.width)]for y in range(new_image.height)]

        dx = (-new_image.width * cos_theta -
              new_image.height * sin_theta + self.width) * 0.5
        dy = (new_image.width * sin_theta -
              new_image.height * cos_theta + self.height) * 0.5

        for y in range(new_image.height):
            y_sin_theta = y * sin_theta
            y_cos_theta = y * cos_theta
            for x in range(new_image.width):
                # 这里选择了临近插值法
                x0 = round(x * cos_theta + y_sin_theta + dx)
                y0 = round(-x * sin_theta + y_cos_theta + dy)
                # 注意python的List有个反向索引的功能
                if y0 >= 0 and x0 >= 0:
                    try:
                        new_image.data[y][x] = self.data[y0][x0]
                    except Exception:
                        # 效果相当于让 y0,x0不要上溢
                        continue

        new_image.update_padding_num()
        new_image.update_file_size()
        new_image.updateBytes()
        new_image.updateInt()
        new_image.generate_24()
        return new_image


if __name__ == '__main__':
    pic = open('res/256128.bmp', 'rb')
    bmp = BMP(pic)
    # bmp.rotate(45)
    # bmp.rotate(-90)
    # bmp.resize(99,99)
    bmp.graying()
    # bmp.showAttribute()
    # bmp.show_data()
    # print(bmp.data)
    pic.close()
